/**
 * \brief   x86emu lib bindings
 * \author  Sebastian Sumpf
 * \date    2007-09-11
 */

/*
 * Copyright (C) 2007-2009 Sebastian Sumpf
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */
#include <base/printf.h>
#include <base/env.h>
#include <io_port_session/connection.h>
#include <util/list.h>
#include "framebuffer.h"
#include "vesa.h"


namespace X86emu {
#include"x86emu/x86emu.h"
}

using namespace Vesa;
using namespace Genode;
using X86emu::x86_mem;

struct X86emu::X86emu_mem X86emu::x86_mem;

/* istantiate externally used template funciton */
template char* X86emu::virt_addr<char*, uint32_t>(uint32_t addr);

/* verbose output: 0: off, 1: output memory information, 2: + port information
 */
static int const verbose_level = 0;

/*******************************
 ** x86emu lib initialization **
 *******************************/

/**
 * map requested address to local virtual address,
 * return pointer to found virtual address
 */
template <typename OUT, typename IN>
OUT X86emu::virt_addr(IN addr)
{
	addr_t local_addr = static_cast<addr_t>(addr);

	/* retrieve local mapping for given address */
	if (local_addr >= 0 && local_addr < PAGESIZE)
		local_addr += x86_mem.bios_addr();

	else if (local_addr >= PAGESIZE && local_addr < 3*PAGESIZE)
		local_addr += (x86_mem.data_addr() - PAGESIZE);

	else if (local_addr >= VESA_MEM && local_addr < VESA_MEM + VESA_SIZE)
		local_addr += (x86_mem.vga_addr() - VESA_MEM);

	else {
		PWRN("Invalid address 0x%08lx", local_addr);
		local_addr = 0;
	}

	return reinterpret_cast<OUT>(local_addr);
}

/**
 * Memory functions
 */
template <typename T>
static T X86API read(X86emu::u32 addr)
{
	return *X86emu::virt_addr<T*>(addr);
}


template <typename T>
static void X86API write(X86emu::u32 addr, T val)
{
	*(X86emu::virt_addr<T*>(addr)) = val;
}


X86emu::X86EMU_memFuncs memFuncs =
{
	&read, &read, &read,
	&write, &write, &write
};


/**
 * Port class, stores port number + I/O-port object
 */
class FB_Port : public List<FB_Port>::Element, public Io_port_connection
{
	private:

		unsigned short _port;

		/* align 4-byte wise */
		static unsigned short const _align = 4;
		static unsigned short const _align_mask  = ~(_align - 1);

	public:

		FB_Port(unsigned short port) :
			Io_port_connection(port & _align_mask, _align),
			_port(port & _align_mask)
		{ }

		bool match(unsigned short port)
		{
			return (_port == (port & _align_mask));
		}

		void print_port()
		{
			printf("io_port: [%04x,%04x)\n", _port, _port + _align);
		}
};


static List<FB_Port> port_list;

static FB_Port* get_port(unsigned short port)
{

	FB_Port *fb_port  = port_list.first();

	for(; fb_port; fb_port = fb_port->List<FB_Port>::Element::next())
		if(fb_port->match(port))
			break;

	if (fb_port)
		return fb_port;

	fb_port = new (env()->heap()) FB_Port(port);
	port_list.insert(fb_port);

	return fb_port;
}


/**
 * PIO functions
 */
template <typename T>
static T X86API inx(X86emu::X86EMU_pioAddr addr)
{
	T ret;
	unsigned short port = static_cast<unsigned short>(addr);
	FB_Port *fb_port =  get_port(port);

	switch (sizeof(T)) {
		case 1:
			ret = (T)fb_port->inb(port);
			break;
		case 2:
			ret = (T)fb_port->inw(port);
			break;
		default:
			ret = (T)fb_port->inl(port);
	}

	return ret;
}


template <typename T>
static void X86API outx(X86emu::X86EMU_pioAddr addr, T val)
{
	unsigned short port = static_cast<unsigned short>(addr);
	FB_Port *fb_port =  get_port(port);
	switch (sizeof(T)) {
		case 1:
			fb_port->outb(port, val);
			break;
		case 2:
			fb_port->outw(port, val);
			break;
		default:
			fb_port->outl(port, val);
	}
}


X86emu::X86EMU_pioFuncs pioFuncs =
{
  &inx,  &inx,  &inx,
  &outx, &outx, &outx
};


/**
 * Setup memory for x86emu
 */
static int map_code_area(void)
{
	int err;
	Genode::Ram_dataspace_capability ds_cap;
	void *dummy;

	/* map page0 */
	if ((err = Framebuffer_drv::map_io_mem(0x0, PAGESIZE, &dummy))) {
		PERR("Could not map page zero");
		return err;
	}
	x86_mem.bios_addr(dummy);

	/* get data pages form RAM */
	try { ds_cap = Genode::env()->ram_session()->alloc(2*PAGESIZE); }
	catch (Ram_session::Alloc_failed) {
		PERR("Could not allocate dataspace for code");
		return err;
	}

	dummy = Genode::env()->rm_session()->attach(ds_cap);
	x86_mem.data_addr(dummy);

	/* build opcode command */
	uint32_t code = 0;
	code  = 0xcd;        /* int opcode */
	code |= 0x10 << 8;   /* 10h  */
	code |= 0xf4 << 16;  /* ret opcode */
	memcpy(dummy, &code, sizeof(uint32_t));

	/* map graphics memory */
	if ((err = Framebuffer_drv::map_io_mem(VESA_MEM, VESA_SIZE, &dummy))) {
		PERR("Could not map device memory");
		return err;
	}
	x86_mem.vga_addr(dummy);

	return 0;
}


uint16_t X86emu::x86emu_cmd(uint16_t eax, uint16_t ebx, uint16_t ecx,
                            uint16_t edi)
{
	using namespace X86emu;
	M.x86.R_EAX  = eax;          /* int10 function number */
	M.x86.R_EBX  = ebx;
	M.x86.R_ECX  = ecx;
	M.x86.R_EDI  = edi;
	M.x86.R_IP   = 0;            /* address of "int10; ret" */
	M.x86.R_SP   = PAGESIZE;     /* SS:SP pointer to stack */
	M.x86.R_CS   =
	M.x86.R_DS   =
	M.x86.R_ES   =
	M.x86.R_SS   = PAGESIZE >> 4;

	X86EMU_exec();
	return M.x86.R_AX;
}


int X86emu::init(void)
{
	if (map_code_area())
		return -1;

	if (verbose_level) {
		PDBG("--- x86 bios area is [%lx - %lx) ---", x86_mem.bios_addr(),
		      x86_mem.bios_addr() + PAGESIZE);
		PDBG("--- x86 data area is [%lx - %x) ---",
		     x86_mem.data_addr(), 2*PAGESIZE);
		PDBG("--- x86 video area is [%lx - %lx) ---", x86_mem.vga_addr(), 
		     x86_mem.vga_addr() + VESA_SIZE);
	}

	X86emu::M.x86.debug = 0;
	X86emu::X86EMU_setupPioFuncs(&pioFuncs);
	X86emu::X86EMU_setupMemFuncs(&memFuncs);
	return 0;
}


void X86emu::verbose()
{
	if (verbose_level < 2)
		return;

	FB_Port *fb_port  = port_list.first();

	for (; fb_port; fb_port = fb_port->List<FB_Port>::Element::next())
		fb_port->print_port();
}
